<?php
namespace app\common\service;

use think\Log;
use think\Config;
use Exception;

class WeChatPayService
{
    private $config;
    //FastAdmin 微信支付 APIv3 JSAPI/小程序下单 用原生方法不用sdk
    public function __construct()
    {
        $this->config = Config::get('wechat.pay');
        if (empty($this->config)) {
            throw new Exception('微信支付配置未找到');
        }
    }

    /**
     * JSAPI下单
     */
    public function jsapiOrder(array $orderData, string $openid): array
    {
        $url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi';

        $data = [
            'appid'        => $this->config['appid'],
            'mchid'        => $this->config['mchid'],
            'description'   => $orderData['description'],
            'out_trade_no' => $orderData['out_trade_no'],
            'notify_url'   => $this->config['notify_url'],
            'amount'       => [
                'total'    => intval($orderData['total']), // 单位:分
                'currency' => 'CNY'
            ],
            'payer'        => ['openid' => $openid]
        ];

        $result = $this->request('POST', $url, $data);

        if (!isset($result['prepay_id'])) {
            Log::error('微信支付下单失败: ' . json_encode($result));
            throw new Exception($result['message'] ?? '支付下单失败');
        }
        return $this->buildJsapiParams($result['prepay_id']);
    }

    /**
     * 构建JSAPI支付参数
     */
    private function buildJsapiParams(string $prepayId): array
    {
        $timeStamp = time();
        $nonceStr = uniqid();
        $package = 'prepay_id=' . $prepayId;
//        $message = $this->config['appid'] . "\n" .
//            $timeStamp . "\n" .
//            $nonceStr . "\n" .
//            $package . "\n";
//        $sign = $this->sign($message);
        // 生成调起支付签名参数包，返回给前端调起支付
        $sign=$this->generateWechatPaySignature(
            $this->config['appid'],
            (string)$timeStamp,
            $nonceStr,
            $prepayId,
            file_get_contents($this->config['key_path'])
        );


        return [
            'appId'     => $this->config['appid'],
            'timeStamp' => (string)$timeStamp,
            'nonceStr'  => $nonceStr,
            'package'   => $package,
            'signType'  => 'RSA',
            'paySign'   => $sign
        ];
    }

    /**
     * 验证支付回调
     */
    public function verifyNotify(): array
    {
        $headers = getallheaders();
        $headers = array_change_key_case($headers, CASE_LOWER);

        $requiredHeaders = [
            'wechatpay-timestamp',
            'wechatpay-nonce',
            'wechatpay-signature',
            'wechatpay-serial'
        ];

        foreach ($requiredHeaders as $header) {
            if (!isset($headers[$header])) {
                throw new Exception("缺少必要请求头: {$header}");
            }
        }

        $body = file_get_contents('php://input');
        $message = $headers['wechatpay-timestamp'] . "\n" .
            $headers['wechatpay-nonce'] . "\n" .
            $body . "\n";

        // 验证签名
        if (!$this->verifySignature(
            $message,
            $headers['wechatpay-signature'],
            $headers['wechatpay-serial']
        )) {
            throw new Exception('签名验证失败');
        }

        // 解密数据
        $data = json_decode($body, true);
        return $this->decryptData(
            $data['resource']['ciphertext'],
            $data['resource']['associated_data'],
            $data['resource']['nonce']
        );
    }

    /**
     * 发送HTTP请求
     */
    private function request(string $method, string $url, array $data = []): array
    {
        $body = $data ? json_encode($data, JSON_UNESCAPED_UNICODE) : '';
        $auth = $this->buildAuth($method, $url, $body);

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
//            CURLOPT_SSL_VERIFYPEER => true,
//            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_SSLCERTTYPE => 'PEM',
            CURLOPT_CAINFO => $this->config['platform_cert_path'],
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'Content-Type: application/json',
                'User-Agent: FastAdmin/WeChatPay',
                'Authorization: ' . $auth
            ]
        ]);

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        }

        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($error) {
            throw new Exception("CURL Error: " . $error);
        }

        $result = json_decode($response, true);
        if ($status !== 200) {
            throw new Exception($result['message'] ?? '微信支付接口请求失败');
        }

        return $result;
    }

    /**
     * 生成下单签名
     */
    private function sign($message,$nonceStr,$timestamp,$canonicalUrl,$body)
    {
        // 第一种方式生成签名
//        $privateKey = openssl_pkey_get_private(
//            file_get_contents($this->config['key_path'])
//        );
//        openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256);
        //return base64_encode($signature);

        // 第二种方式生成签名
        $privateKey =file_get_contents($this->config['key_path']);
        $signature=$this->generateSignature(
            'POST',
            $canonicalUrl,
            $timestamp,
            $nonceStr,
            $body,
            $privateKey
        );

// 生成Authorization头
//        $authcode = $this->generateAuthHeader(
//            $this->config['mchid'],
//            $this->config['serial_no'],
//            $nonceStr,
//            $timestamp,
//            $signature
//        );


        return $signature;
    }

    /**
     * 生成微信支付V3 API签名
     *
     * @param string $method HTTP请求方法
     * @param string $url 请求URL（不包含域名）
     * @param string $timestamp 时间戳
     * @param string $nonceStr 随机字符串
     * @param string $body 请求报文主体
     * @param string $privateKey 商户API私钥
     * @return string Base64编码的签名
     */
    public static function generateSignature(
        string $method,
        string $url,
        string $timestamp,
        string $nonceStr,
        string $body,
        string $privateKey
    ): string {
        // 构造签名串
        $signatureString = sprintf("%s\n%s\n%s\n%s\n%s\n",
            $method,
            $url,
            $timestamp,
            $nonceStr,
            $body
        );

        // 使用私钥进行SHA256 with RSA签名
        if (!openssl_sign($signatureString, $signature, $privateKey, OPENSSL_ALGO_SHA256)) {
            throw new RuntimeException("签名失败: " . openssl_error_string());
        }

        // 对签名结果进行Base64编码
        return base64_encode($signature);
    }

    /**
     * 验证签名
     *
     * @param string $signature 待验证的签名（Base64编码）
     * @param string $method HTTP请求方法
     * @param string $url 请求URL
     * @param string $timestamp 时间戳
     * @param string $nonceStr 随机字符串
     * @param string $body 请求报文主体
     * @param string $publicKey 公钥
     * @return bool 是否验证通过
     */
    public static function verifySignature(
        string $signature,
        string $method,
        string $url,
        string $timestamp,
        string $nonceStr,
        string $body,
        string $publicKey
    ): bool {
        $signatureString = sprintf("%s\n%s\n%s\n%s\n%s\n",
            $method,
            $url,
            $timestamp,
            $nonceStr,
            $body
        );

        $decodedSignature = base64_decode($signature);

        return openssl_verify(
                $signatureString,
                $decodedSignature,
                $publicKey,
                OPENSSL_ALGO_SHA256
            ) === 1;
    }

    /**
     * 生成Authorization头
     *
     * @param string $mchId 商户号
     * @param string $serialNo 证书序列号
     * @param string $nonceStr 随机字符串
     * @param string $timestamp 时间戳
     * @param string $signature 签名
     * @return string Authorization头
     */
    public static function generateAuthHeader(
        string $mchId,
        string $serialNo,
        string $nonceStr,
        string $timestamp,
        string $signature
    ): string {
        return sprintf(
            'WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",signature="%s",timestamp="%s",serial_no="%s"',
            $mchId,
            $nonceStr,
            $signature,
            $timestamp,
            $serialNo
        );
    }


    // 生成微信支付签名函数
    function generateWechatPaySignature(
        string $appId,
        string $timestamp,
        string $nonceStr,
        string $prepayId,
        string $privateKey
    ): string {
        // 构造签名串
        $signatureString = sprintf("%s\n%s\n%s\nprepay_id=%s\n",
            $appId,
            $timestamp,
            $nonceStr,
            $prepayId
        );

        // 使用私钥进行SHA256 with RSA签名
        openssl_sign($signatureString, $signature, $privateKey, OPENSSL_ALGO_SHA256);

        // 对签名结果进行Base64编码
        return base64_encode($signature);
    }




    /**
     * 验证签名
     */
    private function verifySignature2(string $message, string $signature, string $serial): bool
    {
        $cert = file_get_contents($this->config['platform_cert_path']);
        $publicKey = openssl_pkey_get_public($cert);

        $verified = openssl_verify(
            $message,
            base64_decode($signature),
            $publicKey,
            OPENSSL_ALGO_SHA256
        );

        openssl_free_key($publicKey);
        return $verified === 1;
    }

    /**
     * 解密数据
     */
    private function decryptData(string $ciphertext, string $associatedData, string $nonce): array
    {
        $decrypted = openssl_decrypt(
            base64_decode($ciphertext),
            'aes-256-gcm',
            $this->config['apiv3_key'],
            OPENSSL_RAW_DATA,
            $nonce,
            $associatedData
        );

        return json_decode($decrypted, true);
    }

    /**
     * 构造Authorization
     */
    private function buildAuth(string $method, string $url, string $body = ''): string
    {
        $timestamp = time();
        $nonce = uniqid();
        $urlParts = parse_url($url);
        $canonicalUrl = ($urlParts['path'] ?? '/') . (isset($urlParts['query']) ? '?' . $urlParts['query'] : '');

        $message = $method . "\n" .
            $canonicalUrl . "\n" .
            $timestamp . "\n" .
            $nonce . "\n" .
            $body . "\n";

        $signature = $this->sign($message,$nonce,$timestamp,$canonicalUrl,$body);

        return sprintf(
            'WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $this->config['mchid'],
            $nonce,
            $timestamp,
            $this->config['serial_no'],
            $signature
        );
    }
}